package com.trip.service;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

import com.trip.config.CommonProperties;
import com.trip.dao.tables.User;
import com.trip.dao.tables.pojos.PicDomain;
import com.trip.dao.tables.pojos.UserDomain;
import com.trip.dao.tables.records.PicRecord;
import com.trip.dao.tables.records.UserRecord;
import com.trip.exception.ServiceException;
import com.trip.util.AuthUtil;
import com.trip.util.IDUtil;
import com.trip.util.ImageUtil;
import com.trip.util.StringUtil;
import com.trip.vo.AutoLoginRequest;
import com.trip.vo.PlaceAndExperienceListItemVo;
import com.trip.vo.UserSimpleVo;
import com.trip.vo.UserVo;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.jooq.DSLContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.imageio.ImageIO;

import static com.trip.dao.Tables.FRIEND_SHIP;
import static com.trip.dao.Tables.PIC;
import static com.trip.dao.Tables.PLACE_AND_EXP;
import static com.trip.dao.Tables.USER;
import static com.trip.dao.Tables.USER_COLLECTION;
import static com.trip.dao.Tables.USER_RELATED;

/**
 * Created by hc24 on 2017/9/27.
 */
@Service
@Transactional
public class UserService {
    private static final Logger logger= LoggerFactory.getLogger(UserService.class);

    @Autowired
    private DSLContext dslContext;

    @Autowired
    private CommonProperties properties;

    @Autowired
    private PicService picService;

    @Autowired
    private JavaMailSender javaMailSender;

    public UserVo login(String email,String password){

        UserRecord userRecord=dslContext.selectFrom(USER)
                .where(User.USER.EMAIL.eq(email))
                //.and(User.USER.PASSWORD.eq(password))
                .and(User.USER.PASSWORD.eq(StringUtil.md5(password).substring(0,30)))
                .fetchOne();

        if(userRecord==null)
            throw new ServiceException("Login fail: unknown email or bad password");

        userRecord.setLastedLogin(LocalDateTime.now());
        String authToken=AuthUtil.createAuth(userRecord.into(UserDomain.class));
        userRecord.setAuthToken(authToken);
        userRecord.update();

        userRecord.setPassword(null);
        UserVo userVo= userRecord.into(UserVo.class);

        Integer friendCount= dslContext.selectCount().from(FRIEND_SHIP).where(FRIEND_SHIP.USER_ID.eq(userVo.getId())).fetchOne().value1();
        Integer visitedCount=dslContext.selectCount().from(USER_RELATED).where(USER_RELATED.USERID.eq(userVo.getId()),USER_RELATED.VISITED_TIME.isNotNull()).fetchOne().value1();
        Integer notesCount=dslContext.selectCount().from(USER_RELATED).where(USER_RELATED.USERID.eq(userVo.getId()),USER_RELATED.NOTE_TIME.isNotNull()).fetchOne().value1();

        userVo.setFriendCount(friendCount);
        userVo.setVisitedCount(visitedCount);
        userVo.setNotesCount(notesCount);

        return userVo;
    }

    public void save(UserDomain user){
        user.setPassword(StringUtil.md5(user.getPassword()).substring(0,30));
        if(dslContext.selectCount().from(USER)
                .where(User.USER.EMAIL.eq(user.getEmail()))
                .fetchOne().value1()>0)
            throw new ServiceException("The email has been registered.");

        dslContext.newRecord(USER,user).insert();
    }

    public UserDomain autoLogin(AutoLoginRequest request){
        UserRecord userRecord=null;
        switch (request.getType()){
            case 0:
                userRecord=dslContext.selectFrom(USER)
                        .where(USER.WECHAT_ID.eq(request.getThirdPartId()))
                        .fetchOne();
                break;
            case 1:
                userRecord=dslContext.selectFrom(USER)
                        .where(USER.FACEBOOK_ID.eq(request.getThirdPartId()))
                        .fetchOne();
                break;
            case 2:
                userRecord=dslContext.selectFrom(USER)
                        .where(USER.TWITTER_ID.eq(request.getThirdPartId()))
                        .fetchOne();
                break;
        }

        if(userRecord==null){
            PicDomain picDomain = null;
//            try {
//                if(request.getAvatarUrl()!=null) {
//                    URI uri = URI.create(request.getAvatarUrl());
//                    byte[] bytes = IOUtils.toByteArray(uri);
//                    picDomain = picService.savePic(bytes, "jpg", 2);
//                }
//            } catch (IOException e) {
//                logger.error("Read avatar error.",e);
//                //throw new ServiceException("IO error,Please retry.",e);
//            }

            userRecord=dslContext.newRecord(USER);

            userRecord.setEmail("third part");
            userRecord.setPassword(StringUtil.md5(request.getThirdPartId()).substring(0,30));
            userRecord.setNickName(request.getNickName());
            userRecord.setCreatedAt(LocalDateTime.now());
            userRecord.setAvatar(request.getAvatarUrl());
            switch (request.getType()){
                case 0:
                   userRecord.setWechatId(request.getThirdPartId());
                    break;
                case 1:
                  userRecord.setFacebookId(request.getThirdPartId());
                    break;
                case 2:
                   userRecord.setTwitterId(request.getThirdPartId());
                    break;
            }

            userRecord.insert();
        }

        userRecord.setLastedLogin(LocalDateTime.now());
        String authToken=AuthUtil.createAuth(userRecord.into(UserDomain.class));
        userRecord.setAuthToken(authToken);
        userRecord.update();

        userRecord.setPassword(null);

        return userRecord.into(UserDomain.class);
    }

    @Transactional(readOnly = true)
    public UserDomain query(Integer userId){
        UserRecord ur=getById(userId);
        ur.setPassword(null);
        ur.setAuthToken(null);
        return ur==null?null:ur.into(UserDomain.class);
    }

    private UserRecord getById(Integer userId){
        return dslContext.selectFrom(USER)
                .where(USER.ID.eq(userId)).fetchOne();
    }

    public void update(Integer userId,String nickName,String intro){
        dslContext.update(USER).set(USER.NICK_NAME,nickName).set(USER.INTRO,intro).where(USER.ID.eq(userId)).execute();
    }

    public void changePwd(Integer userId,String newPwd,String oldPwd){
        UserRecord ur=getById(userId);
        if(!ur.getPassword().equals(StringUtil.md5(oldPwd).substring(0,30)))
            throw new ServiceException("The old password is wrong");

        dslContext.update(USER).set(USER.PASSWORD,StringUtil.md5(newPwd).substring(0,30)).where(USER.ID.eq(userId)).execute();
    }

    @Transactional(readOnly = true)
    public List<PlaceAndExperienceListItemVo> queryVisited(Integer userId,Integer yourself){
        return dslContext.select(PLACE_AND_EXP.fields()).select(USER_COLLECTION.ID.nvl2(1,0).as("collected")).from(USER_RELATED)
                .leftJoin(PLACE_AND_EXP).on(PLACE_AND_EXP.ID.eq(USER_RELATED.PAEID))
                .leftJoin(USER_COLLECTION).on(USER_COLLECTION.PAE_ID.eq(PLACE_AND_EXP.ID),USER_COLLECTION.USER_ID.eq(yourself))
                .where(USER_RELATED.USERID.eq(userId))
                .and(USER_RELATED.VISITED.eq(true))
                .orderBy(USER_RELATED.VISITED_TIME.desc())
                .fetchStream().map(e->e.into(PlaceAndExperienceListItemVo.class)).collect(Collectors.toList());
    }


    @Transactional(readOnly = true)
    public List<PlaceAndExperienceListItemVo> queryNotes(Integer userId){
        return dslContext.select(PLACE_AND_EXP.fields()).select(USER_RELATED.NOTE.as("notes")).from(USER_RELATED)
                .leftJoin(PLACE_AND_EXP).on(PLACE_AND_EXP.ID.eq(USER_RELATED.PAEID))
                .where(USER_RELATED.USERID.eq(userId))
                .and(USER_RELATED.NOTE_TIME.isNotNull())
                .fetchStream().map(e->e.into(PlaceAndExperienceListItemVo.class)).collect(Collectors.toList());
    }

    public String updateCover(Integer userId,byte[] coverBytes,String ext){
        ext=ext.indexOf(".")>-1?ext:"."+ext;
        String fileName=String.valueOf(Instant.now().getEpochSecond());
        String folderPath=properties.getPicConfig().getPicFolder()+"/user/";
        try {
            //save file
            File orgFile=new File(folderPath+fileName+ext);
            FileUtils.writeByteArrayToFile(orgFile,coverBytes);
            //File coverFile=new File(folderPath+fileName+"_cover"+ext);
            File avatarFile=new File(folderPath+fileName+"_avatar"+ext);
            //ImageUtil.resize(orgFile,coverFile,360,0.85f);
            ImageUtil.resize(orgFile,avatarFile,128,0.85f);
            BufferedImage orgBI= ImageIO.read(orgFile);
            BufferedImage avaBI=ImageIO.read(avatarFile);


            //save db
            PicRecord coverPicRecord= dslContext.newRecord(PIC);
            coverPicRecord.setId(IDUtil.next());
            coverPicRecord.setType(2);
            coverPicRecord.setFileName(orgFile.getName());
            coverPicRecord.setHeight(Double.valueOf(orgBI.getHeight()));
            coverPicRecord.setWidth(Double.valueOf(orgBI.getWidth()));
            coverPicRecord.insert();

            PicRecord avPicRecord= dslContext.newRecord(PIC);
            avPicRecord.setId(IDUtil.next());
            avPicRecord.setType(2);
            avPicRecord.setFileName(avatarFile.getName());
            avPicRecord.setHeight(Double.valueOf(avaBI.getHeight()));
            avPicRecord.setWidth(Double.valueOf(avaBI.getWidth()));
            avPicRecord.insert();

            dslContext.update(USER).set(USER.AVATAR,avPicRecord.getId().toString()).set(USER.COVER,coverPicRecord.getId()).where(USER.ID.eq(userId)).execute();

            return coverPicRecord.getId();
        } catch (IOException e) {
            throw new ServiceException("IO error,Please retry.");
        }

    }

    @Transactional(readOnly = true)
    public List<UserSimpleVo> queryFriendList(Integer userId){
        return dslContext.select(USER.NICK_NAME,USER.AVATAR,USER.INTRO,USER.ID.as("user_id"))
                .from(FRIEND_SHIP)
                .leftJoin(USER).on(USER.ID.eq(FRIEND_SHIP.FRIEND_ID))
                .where(FRIEND_SHIP.USER_ID.eq(userId))
                .fetchInto(UserSimpleVo.class).stream().peek(e->e.setFriend(true)).collect(Collectors.toList());
    }

    @Transactional(readOnly = true)
    public UserSimpleVo queryUser(Integer yourselfId,Integer userId){
        UserSimpleVo vo= dslContext.select(USER.NICK_NAME,USER.AVATAR,USER.COVER,USER.INTRO,USER.ID.as("user_id"),FRIEND_SHIP.ID.nvl2(1,0).as("friend"))
                .from(USER)
                .leftJoin(FRIEND_SHIP).on(FRIEND_SHIP.FRIEND_ID.eq(USER.ID).and(FRIEND_SHIP.USER_ID.eq(yourselfId)))
                .where(USER.ID.eq(userId))
                .fetchOneInto(UserSimpleVo.class);

        if(vo==null)
            return null;

        Integer friendCount= dslContext.selectCount().from(FRIEND_SHIP).where(FRIEND_SHIP.USER_ID.eq(userId)).fetchOne().value1();
        Integer visitedCount=dslContext.selectCount().from(USER_RELATED).where(USER_RELATED.USERID.eq(userId),USER_RELATED.VISITED_TIME.isNotNull()).fetchOne().value1();
        Integer notesCount=dslContext.selectCount().from(USER_RELATED).where(USER_RELATED.USERID.eq(userId),USER_RELATED.NOTE_TIME.isNotNull()).fetchOne().value1();

        vo.setFriendCount(friendCount);
        vo.setVisitedCount(visitedCount);
        vo.setNotesCount(notesCount);

        return vo;
    }

    @Transactional(readOnly = true)
    public List<UserSimpleVo> queryUserList(Integer userId,String param){
        param=param==null?"":param;
        String likeParam="%"+param+"%";
        return dslContext.select(USER.NICK_NAME,USER.AVATAR,USER.INTRO,USER.ID.as("user_id"),FRIEND_SHIP.ID.nvl2(1,0).as("friend"))
                .from(USER)
                .leftJoin(FRIEND_SHIP).on(FRIEND_SHIP.FRIEND_ID.eq(USER.ID).and(FRIEND_SHIP.USER_ID.eq(userId)))
                .where(USER.EMAIL.like(likeParam).or(USER.NICK_NAME.like(likeParam)))
                .limit(100)
                .fetchInto(UserSimpleVo.class)
                .stream().filter(e->!e.getUserId().equals(userId)).collect(Collectors.toList());
    }

    public void setPwd(String email,String pwd){
        dslContext.update(USER).set(USER.PASSWORD,StringUtil.md5(pwd).substring(0,30)).where(USER.EMAIL.eq(email)).execute();
    }

    public String sendCaptcha(String email){
        if(dslContext.selectCount().from(USER).where(USER.EMAIL.eq(email)).fetchOne().value1()==0)
            throw new ServiceException("Unknown email");

        Random random = new Random();
        int code = random.nextInt(9999)%(9999-1000+1) + 1000;

        SimpleMailMessage message=new SimpleMailMessage();
        message.setSubject("Forgot Password");
        message.setText("Your verification code is: "+code);
        message.setCc(email);
        message.setFrom("tartisan_group@outlook.com");

        Executors.newSingleThreadExecutor().submit(()->{
            logger.info("send code "+code+" to "+email);
            javaMailSender.send(message);
        });

        AuthUtil.addCode(email,code+"");

        return String.valueOf(code);
    }

}
